import { describe, expect, test } from 'vitest';
import {
  FieldData,
  invalidFieldShareabilityError,
  ObjectDefinitionData,
  parse,
  ROUTER_COMPATIBILITY_VERSION_ONE,
  Subgraph,
} from '../../../src';
import { KEY_DIRECTIVE, OPENFED_FIELD_SET, SCHEMA_QUERY_DEFINITION, SHAREABLE_DIRECTIVE } from '../utils/utils';
import {
  federateSubgraphsFailure,
  federateSubgraphsSuccess,
  normalizeString,
  normalizeSubgraphSuccess,
  schemaToSortedNormalizedString,
} from '../../utils/utils';

describe('@shareable directive tests', () => {
  describe('Normalization tests', () => {
    test('that @shareable declared on the object level applies to all its defined fields #1.1', () => {
      const { schema } = normalizeSubgraphSuccess(naaaa, ROUTER_COMPATIBILITY_VERSION_ONE);
      expect(schemaToSortedNormalizedString(schema)).toBe(
        normalizeString(
          SHAREABLE_DIRECTIVE +
            `
         type Object {
          nonShareableFieldOne: Boolean!
          nonShareableFieldThree: Boolean
          nonShareableFieldTwo(
            argOne: Int
            """This is a description for Object.nonShareableFieldTwo.argTwo"""
            argTwo: Boolean!
          ): Float!
          """This is the description for Object.shareableFieldFour"""
          shareableFieldFour: String! @shareable
          shareableFieldOne(argOne: String!, argTwo: Boolean!): String @shareable
          shareableFieldThree: Float @shareable
          shareableFieldTwo: Int! @shareable
        }
      `,
        ),
      );
    });

    test('that @shareable declared on the object level applies to all its defined fields #1.2', () => {
      const { schema } = normalizeSubgraphSuccess(nbaaa, ROUTER_COMPATIBILITY_VERSION_ONE);
      expect(schemaToSortedNormalizedString(schema)).toBe(
        normalizeString(
          SHAREABLE_DIRECTIVE +
            `
         type Object {
          nonShareableFieldOne: Boolean!
          nonShareableFieldThree: Boolean
          nonShareableFieldTwo(
            argOne: Int
            """This is a description for Object.nonShareableFieldTwo.argTwo"""
            argTwo: Boolean!
          ): Float!
          """This is the description for Object.shareableFieldFour"""
          shareableFieldFour: String! @shareable
          shareableFieldOne(argOne: String!, argTwo: Boolean!): String @shareable
          shareableFieldThree: Float @shareable
          shareableFieldTwo: Int! @shareable
        }
      `,
        ),
      );
    });

    test('that @shareable declared on both the parent and field level is not repeated', () => {
      const { schema } = normalizeSubgraphSuccess(subgraphD, ROUTER_COMPATIBILITY_VERSION_ONE);
      expect(schemaToSortedNormalizedString(schema)).toBe(
        normalizeString(
          KEY_DIRECTIVE +
            SHAREABLE_DIRECTIVE +
            `
           type Entity @key(fields: "id") {
            field: String! @shareable
            id: ID! @shareable
           }
          ` +
            OPENFED_FIELD_SET,
        ),
      );
    });

    test('that @shareable can be repeated', () => {
      const { schema } = normalizeSubgraphSuccess(subgraphE, ROUTER_COMPATIBILITY_VERSION_ONE);
      expect(schemaToSortedNormalizedString(schema)).toStrictEqual(
        normalizeString(
          KEY_DIRECTIVE +
            SHAREABLE_DIRECTIVE +
            `
          type Entity @key(fields: "id") {
            field: String! @shareable
            id: ID!
            name: String! @shareable
          }
        ` +
            OPENFED_FIELD_SET,
        ),
      );
    });
  });

  describe('Federation tests', () => {
    test('that @shareable functions with extensions correctly #1.1', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphA, subgraphB, subgraphC],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });

    test('that @shareable functions with extensions correctly #1.2', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphA, subgraphC, subgraphB],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });

    test('that @shareable functions with extensions correctly #1.3', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphB, subgraphA, subgraphC],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });

    test('that @shareable functions with extensions correctly #1.4', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphB, subgraphC, subgraphA],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });

    test('that @shareable functions with extensions correctly #1.5', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphC, subgraphA, subgraphB],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });

    test('that @shareable functions with extensions correctly #1.6', () => {
      const { federatedGraphSchema } = federateSubgraphsSuccess(
        [subgraphC, subgraphB, subgraphA],
        ROUTER_COMPATIBILITY_VERSION_ONE,
      );
      expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
        normalizeString(
          SCHEMA_QUERY_DEFINITION +
            `
        type Entity implements Interface {
          field: String!
          id: ID!
        }
        
        interface Interface {
          field: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
        ),
      );
    });
  });

  test('that an error is returned if a V2 implicit key field is not declared @shareable #1.1', () => {
    const { errors } = federateSubgraphsFailure([subgraphF, subgraphG], ROUTER_COMPATIBILITY_VERSION_ONE);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toStrictEqual(
      invalidFieldShareabilityError(
        {
          name: 'Entity',
          fieldDataByName: new Map<string, FieldData>([
            [
              'name',
              {
                isShareableBySubgraphName: new Map<string, boolean>([
                  ['subgraph-f', true],
                  ['subgraph-g', false],
                ]),
              } as FieldData,
            ],
          ]),
        } as ObjectDefinitionData,
        new Set<string>(['id', 'name', 'field']),
      ),
    );
  });

  test('that an error is returned if a V2 implicit key field is not declared @shareable #1.2', () => {
    const { errors } = federateSubgraphsFailure([subgraphG, subgraphF], ROUTER_COMPATIBILITY_VERSION_ONE);
    expect(errors).toHaveLength(1);
    expect(errors[0]).toStrictEqual(
      invalidFieldShareabilityError(
        {
          name: 'Entity',
          fieldDataByName: new Map<string, FieldData>([
            [
              'name',
              {
                isShareableBySubgraphName: new Map<string, boolean>([
                  ['subgraph-f', true],
                  ['subgraph-g', false],
                ]),
              } as FieldData,
            ],
          ]),
        } as ObjectDefinitionData,
        new Set<string>(['id', 'name', 'field']),
      ),
    );
  });

  test('that an @external key field does not contribute to @shareable errors #1.1', () => {
    const { federatedGraphSchema } = federateSubgraphsSuccess([subgraphG, subgraphH], ROUTER_COMPATIBILITY_VERSION_ONE);
    expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
      normalizeString(
        SCHEMA_QUERY_DEFINITION +
          `
        type Entity {
          field: String!
          id: ID!
          name: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
      ),
    );
  });

  test('that an @external key field does not contribute to @shareable errors #1.2', () => {
    const { federatedGraphSchema } = federateSubgraphsSuccess([subgraphH, subgraphG], ROUTER_COMPATIBILITY_VERSION_ONE);
    expect(schemaToSortedNormalizedString(federatedGraphSchema)).toBe(
      normalizeString(
        SCHEMA_QUERY_DEFINITION +
          `
        type Entity {
          field: String!
          id: ID!
          name: String!
        }
        
        type Query {
          entities: [Entity!]!
        }
        `,
      ),
    );
  });

  test('that an @external key field does not contribute to @shareable errors #1.3', () => {
    const result = federateSubgraphsFailure([subgraphG, subgraphH, subgraphI], ROUTER_COMPATIBILITY_VERSION_ONE);
    expect(result.success).toBe(false);
    expect(result.errors).toHaveLength(1);
    expect(result.errors[0]).toStrictEqual(
      invalidFieldShareabilityError(
        {
          name: 'Entity',
          fieldDataByName: new Map<string, FieldData>([
            [
              'name',
              {
                isShareableBySubgraphName: new Map<string, boolean>([
                  ['subgraph-g', false],
                  ['subgraph-h', true],
                  ['subgraph-i', false],
                ]),
              } as FieldData,
            ],
          ]),
        } as ObjectDefinitionData,
        new Set<string>(['id', 'name', 'field']),
      ),
    );
  });
});

const naaaa: Subgraph = {
  name: 'naaaa',
  url: '',
  definitions: parse(`
    type Object {
      shareableFieldOne(
        argOne: String!
        argTwo: Boolean!
      ): String @shareable
      nonShareableFieldOne: Boolean!
    }
    
    extend type Object @shareable {
      shareableFieldTwo: Int!
      shareableFieldThree: Float
    }
    
    extend type Object @shareable {
      """
        This is the description for Object.shareableFieldFour
      """
      shareableFieldFour: String!
    }
    
    extend type Object {
      nonShareableFieldTwo(
        argOne: Int
        """This is a description for Object.nonShareableFieldTwo.argTwo"""
        argTwo: Boolean!
      ): Float!
      nonShareableFieldThree: Boolean
    }
  `),
};

const nbaaa: Subgraph = {
  name: 'nbaaa',
  url: '',
  definitions: parse(`
    extend type Object @shareable {
      """
        This is the description for Object.shareableFieldFour
      """
      shareableFieldFour: String!
    }
    
    extend type Object {
      nonShareableFieldTwo(argOne: Int, """This is a description for Object.nonShareableFieldTwo.argTwo""" argTwo: Boolean!): Float!
      nonShareableFieldThree: Boolean
    }
    
    extend type Object @shareable {
      shareableFieldTwo: Int!
      shareableFieldThree: Float
    }
    
    type Object {
      shareableFieldOne(argOne: String!, argTwo: Boolean!): String @shareable
      nonShareableFieldOne: Boolean!
    }
  `),
};

const subgraphA: Subgraph = {
  name: 'subgraph-a',
  url: '',
  definitions: parse(`
    type Query {
      entities: [Entity!]!
    }
    
    type Entity @key(fields: "id") {
      id: ID!
      field: String! @shareable
    }
  `),
};

const subgraphB: Subgraph = {
  name: 'subgraph-b',
  url: '',
  definitions: parse(`
    interface Interface {
      field: String!
    }
    
    extend type Entity implements Interface @key(fields: "id") {
      id: ID!
      field: String! @external
    }
  `),
};

const subgraphC: Subgraph = {
  name: 'subgraph-c',
  url: '',
  definitions: parse(`
    interface Interface {
      field: String!
    }
    
    extend type Entity implements Interface @key(fields: "id") @shareable {
      id: ID!
      field: String!
    }
  `),
};

const subgraphD: Subgraph = {
  name: 'subgraph-d',
  url: '',
  definitions: parse(`
    type Entity @key(fields: "id") @shareable {
      id: ID!
      field: String! @shareable
    }
  `),
};

const subgraphE: Subgraph = {
  name: 'subgraph-e',
  url: '',
  definitions: parse(`
    type Entity @key(fields: "id") {
      id: ID!
      field: String! @shareable @shareable
    }
    
    extend type Entity @shareable {
      name: String!
    }
  `),
};

const subgraphF: Subgraph = {
  name: 'subgraph-f',
  url: '',
  definitions: parse(`
    type Query {
      entities: [Entity!]!
    }
    
    type Entity @extends @key(fields: "id name") {
      id: ID! @external
      name: String! @external
      field: String! @shareable
    }
  `),
};

const subgraphG: Subgraph = {
  name: 'subgraph-g',
  url: '',
  definitions: parse(`
    type Entity @key(fields: "id") {
      id: ID!
      name: String!
      field: String! @shareable
    }
  `),
};

const subgraphH: Subgraph = {
  name: 'subgraph-h',
  url: '',
  definitions: parse(`
    type Query {
      entities: [Entity!]!
    }
    
    type Entity @key(fields: "id name") {
      id: ID!
      name: String! @external
      field: String! @shareable
    }
  `),
};

const subgraphI: Subgraph = {
  name: 'subgraph-i',
  url: '',
  definitions: parse(`
    type Entity @key(fields: "id") {
      id: ID!
      name: String!
      field: String! @shareable
    }
  `),
};
